/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.ci.core.dao.jpa;

import com.ci.core.dao.search.ExampleOptions;
import com.ci.core.dao.search.Filter;
import com.ci.core.dao.search.ISearch;
import com.ci.core.dao.search.MetadataUtil;
import com.ci.core.dao.search.Search;
import com.ci.core.dao.search.SearchResult;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;

/**
 * @author Lang Yu
 * @description
 * @date Aug 31, 2010 1:58:11 PM
 * @version 1.0
 */
public abstract class GenericDAOImpl<T, ID extends Serializable> implements GenericDAO<T, ID> {
    // 等着子类进行存储单元的选择以及存储操作，需要重写该方法来完成

    protected abstract EntityManager em();
    // JPA中需要使用的元数据
    private static MetadataUtil meta;

    protected MetadataUtil getMetadataUtil() {
        if (meta == null) {
            meta = new JPAAnnotationMetadataUtil();
        }
        return meta;
    }
    private static JPASearchProcessor searchProcessor;

    protected JPASearchProcessor getSearchProcessor() {
        if (searchProcessor == null) {
            searchProcessor = new JPASearchProcessor(getMetadataUtil());
        }
        return searchProcessor;
    }
    // 得到需要持久化的类型
    protected Class<T> persistentClass = (Class<T>) DAOUtil.getTypeArguments(GenericDAOImpl.class, this.getClass()).get(0);

    @Override
    public T find(ID id) {
        return em().find(persistentClass, id);
    }

    @Override
    public T[] find(ID... ids) {
        Object[] retList = (Object[]) Array.newInstance(persistentClass, ids.length);
        for (Object entity : Ids("select _it_", persistentClass, ids)) {
            Serializable id = getMetadataUtil().getId(entity);
            for (int i = 0; i < ids.length; i++) {
                if (id.equals(ids[i])) {
                    retList[i] = entity;
                }
            }
        }
        return (T[]) retList;
    }

    @Override
    public T getReference(ID id) {
        return em().getReference(persistentClass, id);
    }

    @Override
    public T[] getReference(ID... ids) {
        T[] retList = (T[]) Array.newInstance(persistentClass, ids.length);
        for (int i = 0; i < ids.length; i++) {
            retList[i] = getReference(ids[i]);
        }
        return retList;
    }

    @Override
    public void persist(T... entities) {
        for (Object entity : entities) {
            if (entity != null) {
                em().persist(entity);
            }
        }
    }

    @Override
    public T merge(T entity) {
        return em().merge(entity);
    }

    @Override
    public T[] merge(T... entities) {
        T[] retList = (T[]) Array.newInstance(persistentClass, entities.length);
        for (int i = 0; i < entities.length; i++) {
            retList[i] = merge(entities[i]);
        }
        return retList;
    }

    @Override
    public T save(T entity) {
        if (entity == null) {
            return null;
        }
        if (em().contains(entity)) {
            return entity;
        }
        Serializable id = getMetadataUtil().getId(entity);
        if (!validId(id)) {
            em().persist(entity);
            return entity;
        }
        T prev = em().find((Class<T>) entity.getClass(), id);
        if (prev == null) {
            em().persist(entity);
            return entity;
        } else {
            return em().merge(entity);
        }
    }

    @Override
    public T[] save(T... entities) {
        T[] retList = (T[]) Array.newInstance(persistentClass, entities.length);
        for (int i = 0; i < entities.length; i++) {
            retList[i] = save(entities[i]);
        }
        return retList;
    }

    @Override
    public boolean remove(T entity) {
        if (entity != null) {
            if (em().contains(entity)) {
                em().remove(entity);
                return true;
            } else {
                Serializable id = getMetadataUtil().getId(entity);
                return removeById((ID) id);
            }
        }
        return false;
    }

    @Override
    public void remove(T... entities) {
        for (T entity : entities) {
            remove(entity);
        }
    }

    @Override
    public boolean removeById(ID id) {
        if (id != null) {
            Query query = em().createQuery("select id from " + persistentClass.getName() + " where id = ?").setParameter(1, id);
            if (!query.getResultList().isEmpty()) {
                em().remove(em().getReference(persistentClass, id));
                return true;
            }
        }
        return false;
    }

    @Override
    public void removeByIds(ID... ids) {
        for (ID id : ids) {
            removeById(id);
        }
    }

    @Override
    public List<T> findAll() {
        return em().createQuery("select _it_ from " + persistentClass.getName() + " _it_").getResultList();
    }

    @Override
    public <RT> List<RT> search(ISearch search) {
        if (search == null) {
            return (List<RT>) findAll();
        }
        if (search == null) {
            throw new NullPointerException("Search is null.");
        }
        if (persistentClass == null) {
            throw new NullPointerException("Search class is null.");
        }
        if (search.getSearchClass() != null && !search.getSearchClass().equals(persistentClass)) {
            throw new IllegalArgumentException("Search class does not match expected type: " + persistentClass.getName());
        }

        return getSearchProcessor().search(em(), persistentClass, search);
    }

    @Override
    public <RT> SearchResult<RT> searchAndCount(ISearch search) {
        if (search == null) {
            SearchResult<RT> result = new SearchResult<RT>();
            result.setResult((List<RT>) findAll());
            result.setTotalCount(result.getResult().size());
            return result;
        }
        if (search == null) {
            throw new NullPointerException("Search is null.");
        }
        if (search.getSearchClass() == null) {
            throw new NullPointerException("Search class is null.");
        }

        return getSearchProcessor().searchAndCount(em(), search);
    }

    @Override
    public int count(ISearch search) {
        if (search == null) {
            search = new Search();
        }
        if (search == null) {
            throw new NullPointerException("Search is null.");
        }
        if (persistentClass == null) {
            throw new NullPointerException("Search class is null.");
        }
        if (search.getSearchClass() != null && !search.getSearchClass().equals(persistentClass)) {
            throw new IllegalArgumentException("Search class does not match expected type: " + persistentClass.getName());
        }

        return getSearchProcessor().count(em(), persistentClass, search);
    }

    @Override
    public <RT> RT searchUnique(ISearch search) {
        if (search == null) {
            throw new NullPointerException("Search is null.");
        }
        if (persistentClass == null) {
            throw new NullPointerException("Search class is null.");
        }
        if (search.getSearchClass() != null && !search.getSearchClass().equals(persistentClass)) {
            throw new IllegalArgumentException("Search class does not match expected type: " + persistentClass.getName());
        }

        return (RT) getSearchProcessor().searchUnique(em(), persistentClass, search);
    }

    @Override
    public boolean isAttached(T entity) {
        return em().contains(entity);
    }

    @Override
    public Filter getFilterFromExample(T example) {
        return this.getSearchProcessor().getFilterFromExample(example);
    }

    @Override
    public Filter getFilterFromExample(T example, ExampleOptions options) {
        return this.getSearchProcessor().getFilterFromExample(example, options);
    }

    @Override
    public void refresh(T... entities) {
        for (Object entity : entities) {
            if (entity != null) {
                em().refresh(entity);
            }
        }
    }

    @Override
    public void flush() {
        em().flush();
    }
    // 私有辅助函数，多个id查询来拼接字符串

    private List<?> Ids(String select, Class<?> type, Serializable[] ids) {
        List<Serializable> nonNulls = new LinkedList<Serializable>();
        StringBuilder sb = new StringBuilder(select);
        sb.append(" from ");
        sb.append(type.getName());
        sb.append(" _it_ where ");
        for (Serializable id : ids) {
            if (id != null) {
                if (nonNulls.isEmpty()) {
                    sb.append("id = ?");
                } else {
                    sb.append(" or id = ?");
                }
                nonNulls.add(id);
            }
        }
        if (nonNulls.isEmpty()) {
            return new ArrayList<Object>();
        }
        Query query = em().createQuery(sb.toString());
        int index = 1;
        for (Serializable id : nonNulls) {
            query.setParameter(index++, id);
        }
        return query.getResultList();
    }

    private boolean validId(Serializable id) {
        if (id == null) {
            return false;
        }
        if (id instanceof Number && ((Number) id).equals(0)) {
            return false;
        }
        if (id instanceof String && "".equals(id)) {
            return false;
        }
        return true;
    }
}
